#pragma once

#include <maths.h>

class mesh
{
public:
	typedef std::vector<vector3> point3_vec;
	typedef std::vector<vector2> point2_vec;
	typedef std::vector<DWORD> index_vec;
	typedef std::vector<size_t> bones;
	typedef std::vector<bones> bone_vec;
	typedef std::vector<float> weights;
	typedef std::vector<weights> weight_vec;
	typedef std::vector<size_t> bind_id;

public:
	mesh();
	mesh(const std::string &name);
	~mesh();

	void clear();

	bool empty() const;
	bool has_coords() const;
	bool has_normals() const;
	bool has_texcoords() const;

	void set_name(const std::string &name);

	void set_coord(int id, float x, float y, float z);
	void set_normal(int id, float x, float y, float z);
	void set_texcoord(int id, float u, float v);
	void set_bone_weight(int id, int bone, int bone_id, float weight);
	void add_index(int id);
	void set_vertex_binding(size_t vert, size_t id);
	void set_skeleton(skeleton_ptr skl);
	void set_mat_id(size_t id);

	std::string get_name() const;

	const bounding_box & get_bbox() const;

	size_t get_num_vertices() const;
	size_t get_num_indices() const;

	const point3_vec &get_coords() const;
	const point3_vec &get_normals() const;
	const point2_vec &get_texcoords() const;
	const index_vec &get_faces() const;
	const bone_vec &get_bones() const;
	const weight_vec &get_weights() const;
	size_t get_vertex_binding(size_t vert) const;
	skeleton_ptr get_skeleton() const;

	vector3 get_coord(size_t id) const;
	vector3 get_normal(size_t id) const;
	vector2 get_texcoord(size_t id) const;
	size_t get_mat_id() const;

	DWORD	get_idx(size_t pos) const;

	void merge(const mesh &m);

	void build_normals();

private:
	std::string		m_name;

	bounding_box	m_bbox;

	point3_vec		m_coords;
	point3_vec		m_normals;
	point2_vec		m_texcoords;
	bone_vec		m_bones;
	weight_vec		m_weights;

	index_vec		m_indices;

	bind_id			m_vertex_bindings;

	skeleton_ptr	m_skeleton;

	size_t			m_mat_id;
};

inline mesh::mesh() { }

inline mesh::mesh(const std::string &name) : m_name(name) { }

inline mesh::~mesh() { }

inline void mesh::clear()
{
	m_coords.clear();
	m_normals.clear();
	m_texcoords.clear();
	m_indices.clear();
	m_bones.clear();
	m_weights.clear();
	m_mat_id = -1;
	m_vertex_bindings.clear();
}

inline bool mesh::empty() const { return !(has_coords() && has_normals() && has_texcoords()); }

inline bool mesh::has_coords() const { return !m_coords.empty(); }

inline bool mesh::has_normals() const { return !m_normals.empty(); }

inline bool mesh::has_texcoords() const { return !m_texcoords.empty(); }

inline void mesh::set_coord(int id, float x, float y, float z)
{
	if (id >= m_coords.size())
		m_coords.resize(id + 1);
	m_coords[id] = vector3(x, y, z);
	m_bbox.merge(bounding_box(x, y, z, x, y, z));
}

inline void mesh::set_normal(int id, float x, float y, float z)
{
	if (id >= m_normals.size())
		m_normals.resize(id + 1);
	m_normals[id] = vector3(x, y, z);
}

inline void mesh::set_texcoord(int id, float u, float v)
{
	if (id >= m_texcoords.size())
		m_texcoords.resize(id + 1);
	m_texcoords[id] = vector2(u, v);
}

inline void mesh::set_bone_weight(int id, int bone, int bone_id, float weight)
{
	if(m_bones.size() <= id)
		m_bones.resize(id + 1);
	if(m_bones[id].size() <= bone)
		m_bones[id].resize(bone + 1);
	m_bones[id][bone] = bone_id;
	if(m_weights.size() <= id)
		m_weights.resize(id + 1);
	if(m_weights[id].size() <= bone)
		m_weights[id].resize(bone + 1);
	m_weights[id][bone] = weight;
}

inline void mesh::set_vertex_binding(size_t id, size_t vert)
{
	if (id >= m_vertex_bindings.size())
		m_vertex_bindings.resize(id + 1);
	m_vertex_bindings[id] = vert;
}

inline void mesh::set_mat_id(size_t id) { m_mat_id = id; }

inline void mesh::add_index(int id) { m_indices.push_back(id); }

inline const mesh::point3_vec &mesh::get_coords() const { return m_coords; }

inline const mesh::point3_vec &mesh::get_normals() const { return m_normals; }

inline const mesh::point2_vec &mesh::get_texcoords() const { return m_texcoords; }

inline const mesh::index_vec &mesh::get_faces() const { return m_indices; }

inline vector3 mesh::get_coord(size_t id) const { return m_coords[id]; }

inline vector3 mesh::get_normal(size_t id) const { return m_normals[id]; }

inline vector2 mesh::get_texcoord(size_t id) const { return m_texcoords[id]; }

inline size_t mesh::get_num_vertices() const { return m_coords.size(); }

inline size_t mesh::get_num_indices() const { return m_indices.size(); }

inline void mesh::set_name(const std::string &name) { m_name = name; }

inline std::string mesh::get_name() const { return m_name; }

inline DWORD mesh::get_idx(size_t pos) const { return m_indices[pos]; }

inline const bounding_box & mesh::get_bbox() const { return m_bbox; }

inline const mesh::bone_vec &mesh::get_bones() const { return m_bones; }

inline const mesh::weight_vec &mesh::get_weights() const { return m_weights; }

inline size_t mesh::get_vertex_binding(size_t id) const { return m_vertex_bindings[id]; }

inline size_t mesh::get_mat_id() const { return m_mat_id; }

inline void mesh::merge(const mesh &m)
{
	if(m.empty())
		return;
	size_t offset = m_coords.size();
	for(size_t i = 0; i < m.m_indices.size(); i++)
		m_indices.push_back(offset + m.m_indices[i]);
	m_coords.insert(m_coords.end(), m.m_coords.begin(), m.m_coords.end());
	m_normals.insert(m_normals.end(), m.m_normals.begin(), m.m_normals.end());
	m_texcoords.insert(m_texcoords.end(), m.m_texcoords.begin(), m.m_texcoords.end());
	m_vertex_bindings.insert(m_vertex_bindings.end(), m.m_vertex_bindings.begin(), m.m_vertex_bindings.end());
	m_bbox.merge(m.m_bbox);
	if(m_name.empty())
		m_name = m.m_name;
}

inline void mesh::set_skeleton(skeleton_ptr skl) { m_skeleton = skl; }

inline skeleton_ptr mesh::get_skeleton() const { return m_skeleton; }

inline void mesh::build_normals()
{
	if(get_num_vertices() == 0)
		return;

	std::vector<std::vector<vector3>> normals;
	normals.resize(get_num_vertices());
	for(size_t i = 0; i < get_num_indices() / 3; i++) {
		vector3 vert[3];
		for(int j = 0; j < 3; j++) {
			int idx = get_idx(i * 3 + j);
			vert[j] = get_coord(idx);
		}

		vector3 v1 = vector3((vert[0].x - vert[2].x), vert[0].y - vert[2].y, vert[0].z - vert[2].z);
		vector3 v2 = vector3((vert[2].x - vert[1].x), vert[2].y - vert[1].y, vert[2].z - vert[1].z);
		vector3 norm = v1.cross(v2);

		for(int j = 0; j < 3; j++) {
			int idx = get_idx(i * 3 + j);
			normals[idx].push_back(norm);
		}
	}

	m_normals.clear();
	m_normals.resize(get_num_vertices());
	for(size_t i = 0; i < get_num_vertices(); i++) {
		std::vector<vector3> &vec = normals[i];
		vector3 n;
		for(size_t j = 0; j < vec.size(); j++)
			 n += vec[j];
		n /= float(vec.size());
		n.normalize();
		n *= -1.0f;
		m_normals[i] = n;
	}
}